/* child.c */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <wait.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "log.h"
#include "http.h"
#include "child.h"
#include "init.h"

#define FD_OFFSET       7   /* first connection fd in child */

child       *cptr = NULL;   /* array of child structures */
static int  nchildren = 0;  /* current count of children */

extern int conn_max;        /* maximum connection number */
extern int child_max;       /* maximum child number */
extern int child_min;       /* minimum child number */
extern int child_init;      /* initial child number */
extern int child_interval;  /* child_interval for check child state */

static void child_closeconn(int connfd, int sockfd, int *connfd_cnt)
{
    close(connfd);
    *connfd_cnt = *connfd_cnt - 1;
    if (write(sockfd, connfd_cnt, sizeof(int)) < 0)
        log_error("write connfd_cnt %d error: %s", connfd_cnt, 
            strerror(errno));
}

static void child_initrequest(int connfd, http_request *request[])
{
    int             index;
    http_request    *hp;

    index = connfd - FD_OFFSET;
    if (request[index] == NULL) {
        if ((hp = malloc(sizeof(http_request))) == NULL) {
            log_error("malloc http_request error: %s", strerror(errno));
            exit(1);
        }
        memset(hp, 0, sizeof(http_request));
        request[index] = hp;
    }
}

static void child_closerequest(int connfd, http_request *request[])
{
    int             index;
    http_request    *hp;

    index = connfd - FD_OFFSET;
    hp = request[index];
    free(hp);
    request[index] = NULL;
}

static void child_initrio(int connfd, rio_t *rio[])
{
    int             index;
    rio_t           *rp;

    index = connfd - FD_OFFSET;
    if (rio[index] == NULL) {
        if ((rp = malloc(sizeof(rio_t))) == NULL) {
            log_error("malloc rio_t error: %s", strerror(errno));
            exit(1);
        }
        memset(rp, 0, sizeof(rio_t));
        rio[index] = rp;
        rio_readinitb(rp, connfd);
    }   
}

static void child_closerio(int connfd, rio_t *rio[])
{
    int             index;
    rio_t           *rp;

    index = connfd - FD_OFFSET;
    rp = rio[index];
    free(rp);
    rio[index] = NULL;
}

static void child_initresponse(int connfd, http_response *response[])
{
    int             index;
    http_response   *hp;

    index = connfd - FD_OFFSET;
    if (response[index] == NULL) {
        if ((hp = malloc(sizeof(http_response))) == NULL) {
            log_error("malloc http_response error: %s", strerror(errno));
            exit(1);
        }
        memset(hp, 0, sizeof(http_response));
        response[index] = hp;
    }
}

static void child_closeresponse(int connfd, http_response *response[])
{
    int             index;
    http_response   *hp;

    index = connfd - FD_OFFSET;
    hp = response[index];
    free(hp);
    response[index] = NULL;
}

static ssize_t child_writefd(int fd, void *ptr, size_t nbytes, int sendfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    union {
        struct cmsghdr  cm;
        char            control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *)CMSG_DATA(cmptr)) = sendfd;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    return (sendmsg(fd, &msg, 0));
}

static ssize_t child_readfd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    ssize_t         n;

    union {
        struct cmsghdr  cm;
        char            control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    if ((n = recvmsg(fd, &msg, 0)) <= 0)
        return n;

    if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL && 
        cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET) {
            log_error("control level != SOL_SOCKET");
            exit(1);
        }

        if (cmptr->cmsg_type != SCM_RIGHTS) {
            log_error("control type != SCM_RIGHTS");
            exit(1);
        }

        *recvfd = *((int *)CMSG_DATA(cmptr));
    }
    else
        *recvfd = -1;       /* descriptor was not passed */

    return n;
}

/* make new child process */
void child_make(int listenfd, fd_set *set, int *maxfd)
{
    int             sockfd[2], rlmax, i;
    pid_t           pid;
    child           *newchild;

    socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);

    if ((pid = fork()) > 0) {
        /* parent, use sockfd[1] */
        close(sockfd[0]);
        newchild = malloc(sizeof(child));
        if (newchild == NULL) {
            log_error("malloc error: %s", strerror(errno));
            child_killall();
            exit(1);
        }

        newchild->child_pid = pid;
        newchild->child_pipefd = sockfd[1];
        newchild->child_stat = 0;
        newchild->child_count = 0;
        newchild->child_time = 0;
        newchild->child_next = cptr;

        cptr = newchild;
        nchildren ++;
        FD_SET(sockfd[1], set);
        if (sockfd[1] > *maxfd)
            *maxfd = sockfd[1];

        log_info("create child[%d]: %d", nchildren, pid);
    }
    else if (pid == 0) {
        /* child, use sockfd[0] */
        close(listenfd);
        rlmax = get_rlmax();
        for (i = sockfd[0]+1; i < rlmax; i++)
            close(i);
        
        child_main(sockfd[0]);
    }
    else {
        log_error("fork error: %s", strerror(errno)); 
        child_killall();
        exit(1);
    }
}

/* main function for child process running */
void child_main(int sockfd)
{
    char                c;
    int                 connfd, epfd, nfds, i, n, connfd_cnt, opt, index;
    struct sockaddr     client_addr;
    socklen_t           client_len;
    struct hostent      *hp;
    struct epoll_event  ev, *events;
    http_request        **request;
    http_response       **response;
    rio_t               **rio;
    
    log_info("child starting...");

    request = calloc(conn_max, sizeof(http_request *));
    if (request == NULL) {
        log_error("calloc request error: %s", strerror(errno));
        exit(1);
    }

    response = calloc(conn_max, sizeof(http_response *));
    if (response == NULL) {
        log_error("calloc response error: %s", strerror(errno));
        exit(1);
    }

    rio = calloc(conn_max, sizeof(rio_t *));
    if (rio == NULL) {
        log_error("calloc rio error: %s", strerror(errno));
        exit(1);
    }

    events = calloc(conn_max, sizeof(struct epoll_event));
    if (events == NULL) {
        log_error("calloc epoll_event error: %s", strerror(errno));
        exit(1);
    }

    epfd = epoll_create(conn_max);
    ev.data.fd = sockfd;
    ev.events = EPOLLIN|EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    connfd_cnt = 0;
    while (1) {
        nfds = epoll_wait(epfd, events, connfd_cnt+1, child_interval);
        for (i = 0; i < nfds; i++) {
            /* get a connection */
            if (events[i].data.fd == sockfd) {
                if ((n = child_readfd(sockfd, &c, 1, &connfd)) != 1) { 
                    log_error("child_readfd error: %s", strerror(errno));
                    exit(1);
                }

                if (connfd < 0) {
                    log_error("no descriptor from child_readfd");
                    exit(1);
                }

                /* set connfd non-blocking */
                opt = fcntl(connfd, F_GETFL, 0);
                if (opt < 0) {
                    log_error("fcntl getfl error: %s", strerror(errno));
                    exit(1);
                }

                opt |= O_NONBLOCK;
                if (fcntl(connfd, F_SETFL, opt) < 0) {
                    log_error("fcntl setfl error: %s", strerror(errno));
                    exit(1);
                }

                /* add connfd to epoll events */
                connfd_cnt ++;
                ev.data.fd = connfd;
                ev.events = EPOLLIN|EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }

            /* read data from connection */
            else if (events[i].events & EPOLLIN) {
                connfd = events[i].data.fd;
                index = connfd - FD_OFFSET;
                log_info("receive request from %d", connfd);

                child_initrequest(connfd, request);
                child_initrio(connfd, rio);
                
                /* read request */
                if ((n = http_readrequest(request[index], rio[index])) 
                    == 1) {
                    child_initresponse(connfd, response);

                    log_info("request from %d: method is %s, uri is %s,"
                        " version is %s", connfd, request[index]->method,
                        request[index]->uri, request[index]->version);

                    /* get remote name and addr */
                    client_len = sizeof(client_addr);
                    getpeername(connfd, &client_addr, &client_len);
                    strcpy(request[index]->remote_addr, inet_ntoa(
                        ((struct sockaddr_in *)&client_addr)->sin_addr));
                    hp = gethostbyaddr(
                        &((struct sockaddr_in *)&client_addr)->sin_addr, 
                        sizeof(struct in_addr), AF_INET);
                    strcpy(request[index]->remote_name, hp->h_name);

                    /* handle the request */
                    http_serve(request[index], response[index]);

                    log_info("handle request from %d", connfd);

                    events[i].events = EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, connfd, &events[i]);
                    child_closerequest(connfd, request);
                    child_closerio(connfd, rio);
                }
                else if (n < 0) {
                    log_error("get request from %d error: %s", connfd, 
                        strerror(errno));
                    epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, &events[i]);
                    child_closerequest(connfd, request);
                    child_closerio(connfd, rio);
                    child_closeconn(connfd, sockfd, &connfd_cnt);
                }
            }

            /* send data */
            else if (events[i].events & EPOLLOUT) {
                connfd = events[i].data.fd;
                index = connfd - FD_OFFSET;
                log_info("send response to %d", connfd);
                    
                /* write response to client */
                if ((n = http_writeresponse(response[index],connfd)) != 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, &events[i]);
                    child_closeresponse(connfd, response);
                    child_closeconn(connfd, sockfd, &connfd_cnt);
                }
            }
        }
    }
}

/* schedule a new connection to a child */
void child_schedule(int connfd, int listenfd, fd_set *set, int *maxfd)
{
    int     fd;
    child   *current, *next;

    /* add a new child process */
    if ((cptr == NULL) || (cptr->child_stat >= conn_max &&
            nchildren < child_max)) {

        /* change to connfd to newfd */
        fd = dup(connfd);
        close(connfd);
        child_make(listenfd, set, maxfd);
        connfd = fd;
    }

    cptr->child_stat ++;
    cptr->child_count ++;
    cptr->child_time = 0;
    
    /* insert the child to a appropriate possion */
    next = cptr;
    while (next->child_next != NULL && next->child_next->child_stat <=
        cptr->child_stat) {
        next = next->child_next; 
    }

    current = cptr;
    if (cptr != next ) {
        cptr = cptr->child_next;
        current->child_next = next->child_next;
        next->child_next = current;
    }

    /* send connfd to the child */
    child_writefd(current->child_pipefd, "", 1, connfd);
    close(connfd);
}

/* delete child, if enforce is 1, ignore child_min */
void child_delete(child *cp, fd_set *set, int enforce)
{
    child *next;

    if (!enforce && nchildren <= child_min)
        return ;

    if (cptr == cp) {
        cptr = cptr->child_next;    
        log_info("child %d delete", cp->child_pid);
        kill(cp->child_pid, SIGTERM);
        FD_CLR(cp->child_pipefd, set);
        close(cp->child_pipefd);
        free(cp);
        nchildren --;
        return ;
    }

    next = cptr;
    while (next != NULL && next->child_next != cp)
        next = next->child_next; 

    if (next == NULL) {
        log_error("not found child which need delete");
        return;
    }

    next->child_next = cp->child_next;
    log_info("child %d delete", cp->child_pid);
    kill(cp->child_pid, SIGTERM);
    FD_CLR(cp->child_pipefd, set);
    close(cp->child_pipefd);
    free(cp);
    nchildren --;
}

/* delete all children */
void child_killall()
{
    child *cp;

    while (cptr != NULL) {
        kill(cptr->child_pid, SIGTERM);
        cp = cptr;
        cptr = cptr->child_next;
        free(cp);
        nchildren --;
    }
}
